#pragma once 
#include <string>
#include "Connection.hpp"
#include "InetAddr.hpp"

//这里就是用来封装一个普通套接字得！
//因为今天这个是单进程多路转接的方案，而且，使用的是TCP协议

//会面临以下几个问题：
// 1.我们没办法保证直接读取的时候，能读到完整的报文！ 又因为这里是用的epoll的ET模式，读一次就得等下次新数据到来才能再读
//   这就导致如果把数据读取到放在每一个读取进入的局部buffer内，会达不到想要的效果 -> 所以，每个fd都要自己的读取缓冲区和写缓冲区 -> 保存历史读取数据
// 2.因为要保证读到完整的报文 -> 引入协议

class Channel : public Connection{
private:
    const static int default_sockfd = -1;
public:
    Channel(int fd, InetAddr& client)
        :_sockfd(fd),
        _client(client)
    {
        //设置fd为非阻塞 -> 使用的ET模式的前置条件
        SetNoBlock(_sockfd);
        //这里不设置关注事件，因为这个一般是用来获取到新链接的时候，
        //加入内核等待的时候才进行对应的设置！
    }

    ~Channel(){}

#define BUFFER_SIZE 1024
    //处理读取的虚方法
    virtual void Recver() override{
        //这里是普通文件描述符的读取  已经被设置为非阻塞
        char buffer[BUFFER_SIZE] = {0};
        //所以，还是需要进行循环读取！
        while(true){
            buffer[0] = 0; //清空缓冲区
            int n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0); //这里flags == 0，但早就被设置成非阻塞了！
            if(n > 0){
                buffer[n] = 0;
                //这里读取成功了，需要把读取出来的数据添加到输入缓冲区
                _inbuffer += buffer;
                LOG(LogLevel::DEBUG) << "读取到一串数据";

                //后序就是需要进行业务的处理了！ //需要引入协议 + 依照协议逻辑处理的回调函数
            }
            else if(n == 0){
                //客户端退出了！
                LOG(LogLevel::DEBUG) << "客户端退出";
                Excepter();
                break;
            }
            else{
                if(errno == EAGAIN || errno == EWOULDBLOCK) break;
                else if(errno == EINTR) continue;
                else {
                    Excepter();
                    LOG(LogLevel::DEBUG) << "返回Reactor的事件派发处";
                    return;
                }
            }
        }

        //然后，读取完之后，就需要对读到的数据进行处理！ -> 这个处理是外界传进来的！
        //把读到的数据(inbuffer)，传给服务来做，然后处理好数据后，把处理好的应答(序列化后)数据写到outbuffer
        if(!_inbuffer.empty()) _outbuffer += _handler_conn(_inbuffer);

        //接下来就可以想办法处理发送的事情了！
        if(!_outbuffer.empty()) Sender();
    }

    //处理写入的虚方法
    virtual void Sender()  override{
        //发送信息在哪里？ -> oubuffer
        //但是这里还需要注意的是：
        //写事件默认是就绪的，不能常设关心写！ -> 因为那样的话就是一直在就绪
        //对于写来说，能写就直接写，如果说，没办法写了，就把写的关心加入到epoll中！
        while(1){
            ssize_t n = send(_sockfd, _outbuffer.c_str(), _outbuffer.size(), 0);
            //n的值其实是发送的字节数
            if(n > 0){
                _outbuffer.erase(0, n);
                //对于outbuffer来说，很可能发完了，也可能还有剩余的
                //如果没有发完就需要继续发
                //发完了就不用(outbuffer空的)
                if(_outbuffer.empty()) break;
            }
            else if(n == 0) break;
            else{
                //n < 0 -> 一定出错吗？
                if(errno == EWOULDBLOCK || errno == EAGAIN) break;  //非阻塞读取的情况下，一轮循环后还是没法写
                else if(errno == EINTR) continue;    //被信号中断了，需要继续读写！
                else Excepter(); //出错，异常处理
            }
        }

        //此时出来的时候，不知道到底还有没有数据遗留在outbuffer内 -> 如果有的话，就说明该文件描述符因为写不就绪导致没法发送
        //所以，如果还有数据，就需要直接把写事件的关心加入到epoll
        //如果写完了呢？ -> 那就继续关心读事件就好了！(这个工作等待下一轮reactor遍历revents数组的时候会做的！)

        //但是，epoll实在reactor这一层的，所以要在这一层来实现修改关心事件的！ -> 用回掉指针
        //注意，只要添加事件EPOLLOUT的关心，下一次默认会触发一次写就绪！
        if(!_outbuffer.empty()) GetReactBackPtr()->EnableReadWrite(_sockfd, true, true);
        else GetReactBackPtr()->EnableReadWrite(_sockfd, true, false);
    }

    //处理异常事件的方法
    virtual void Excepter() override{
        //既然是出现异常了，而且所有的问题都会归到这个Excepter内
        //这个fd出问题了，也就别玩了
        LOG(LogLevel::DEBUG) << "异常处理";
        GetReactBackPtr()->DelConnection(_sockfd);
        LOG(LogLevel::DEBUG) << "异常处理结束";
    }

    virtual int GetSockFd() override{return _sockfd;}

private:
    int _sockfd;

    //直接拿string来模拟缓冲区了，直接把读取到的内容当成字符！
    //如果碰到一些音频、视频等，需要使用vector<char>
    //但是这里简单一点就可以了！
    std::string _inbuffer;  //接收buffer
    std::string _outbuffer; //输出buffer

    //对于普通套接字来说，是要直到通信的对端是谁的！
    InetAddr _client;
};